﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Web;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;

namespace web_uchet.JQgridProcess
{
    public class GridSettings
    {
        public bool _search { get; set; }
        public int rows { get; set; }
        public int page { get; set; }
        public string sidx { get; set; }
        public string sord { get; set; }
        private string _filters;
        public string filters { get { return this._filters; } 
            set { this._filters = value; this.Where = Filter.Create(value); } }
        public long count { get; set; } 
        public Filter Where { get; set; }
        public JQGridFilterType FilterType { get; set; }
        public string FilterParameter { get; set; }
    }
    public enum JQGridFilterType
    {
        All,MyOrders,ClientOrders,Today,Tomorrow,DateRange
    }
    //Rule – представляет одно правило из фильтра
    [DataContract]
    public class Rule
    {
        [DataMember]
        public string field { get; set; }
        [DataMember]
        public string op { get; set; }
        [DataMember]
        public string data { get; set; }
    }

    [DataContract]
    public class Filter
    {
        [DataMember]
        public string groupOp { get; set; }
        [DataMember]
        public Rule[] rules { get; set; }

        public static Filter Create(string jsonData)
        {
            try
            {
                var serializer = new DataContractJsonSerializer(typeof(Filter));
                System.IO.StringReader reader = new System.IO.StringReader(jsonData);
                System.IO.MemoryStream ms =
                    new System.IO.MemoryStream(Encoding.UTF8.GetBytes(jsonData));
                return serializer.ReadObject(ms) as Filter;
            }
            catch
            {
                return null;
            }
        }
    }
    public static class LinqExtensions
    {
        /// <summary>Orders the sequence by specific column and direction.</summary>
        /// <param name="query">The query.</param>
        /// <param name="sortColumn">The sort column.</param>
        /// <param name="ascending">if set to true [ascending].</param>
        public static IQueryable<T> OrderBy<T>(this IQueryable<T> query, string sortColumn, string direction)
        {
            string methodName = string.Format("OrderBy{0}",
                direction.ToLower() == "asc" ? "" : "descending");

            ParameterExpression parameter = Expression.Parameter(query.ElementType, "p");

            MemberExpression memberAccess = null;
            foreach (var property in sortColumn.Split('.'))
                memberAccess = MemberExpression.Property
                   (memberAccess ?? (parameter as Expression), property);

            LambdaExpression orderByLambda = Expression.Lambda(memberAccess, parameter);

            MethodCallExpression result = Expression.Call(
                      typeof(Queryable),
                      methodName,
                      new[] { query.ElementType, memberAccess.Type },
                      query.Expression,
                      Expression.Quote(orderByLambda));

            return query.Provider.CreateQuery<T>(result);
        }


        public static IQueryable<T> Where<T>(this IQueryable<T> query,
            string column, object value, WhereOperation operation)
        {
            if (string.IsNullOrEmpty(column))
                return query;

            ParameterExpression parameter = Expression.Parameter(query.ElementType, "p");

            MemberExpression memberAccess = null;
            foreach (var property in column.Split('.'))
                memberAccess = MemberExpression.Property
                   (memberAccess ?? (parameter as Expression), property);

            //change param value type
            //necessary to getting bool from string
            ConstantExpression filter = Expression.Constant
                (
                    value=Convert.ChangeType(value, memberAccess.Type)
                );

            //switch operation
            Expression condition = null;
            LambdaExpression lambda = null;
            switch (operation)
            {
                //equal ==
                case WhereOperation.Equal:
                    condition = Expression.Equal(memberAccess, filter);
                    lambda = Expression.Lambda(condition, parameter);
                    break;
                //not equal !=
                case WhereOperation.NotEqual:
                    condition = Expression.NotEqual(memberAccess, filter);
                    lambda = Expression.Lambda(condition, parameter);
                    break;
                //string.Contains()
                case WhereOperation.Contains:
                    condition = Expression.Call(memberAccess,
                        typeof(string).GetMethod("Contains"),
                        Expression.Constant(value));
                    lambda = Expression.Lambda(condition, parameter);
                    break;
                case WhereOperation.GreatEqual:                    
                    //condition = Expression.Call(memberAccess,
                    //    typeof(float).GetMethod("CompareTo"),
                    //    Expression.Constant(value));
                    condition=Expression.GreaterThanOrEqual(memberAccess, Expression.Constant(value));
                    lambda = Expression.Lambda(condition, parameter);
                    break;
                case WhereOperation.LessEqual:
                    condition=Expression.LessThanOrEqual(memberAccess, Expression.Constant(value));
                    lambda = Expression.Lambda(condition, parameter);
                    break;
                case WhereOperation.BeginsWith:
                    condition = Expression.Call(memberAccess,
                        typeof(string).GetMethod("StartsWith",new Type[] { typeof(string)}),
                        Expression.Constant(value));
                    lambda = Expression.Lambda(condition, parameter);                    
                    break;
            }


            MethodCallExpression result = Expression.Call(
                   typeof(Queryable), "Where",
                   new[] { query.ElementType },
                   query.Expression,
                   lambda);

            return query.Provider.CreateQuery<T>(result);
        }

        public static T[] GetGridFilteredData<T>(this IQueryable<T> Query, web_uchet.JQgridProcess.GridSettings grid)
        {
            //filtring
            if (grid._search)
            {
                //And
                if (grid.Where.groupOp == "AND")
                    foreach (var rule in grid.Where.rules)
                        Query = Query.Where<T>(
                            rule.field, rule.data,
                            (WhereOperation)StringEnum.Parse(typeof(WhereOperation), rule.op));
                else
                {
                    //Or
                    var temp = (new List<T>()).AsQueryable();
                    foreach (var rule in grid.Where.rules)
                    {
                        var t = Query.Where<T>(
                        rule.field, rule.data,
                        (WhereOperation)StringEnum.Parse(typeof(WhereOperation), rule.op));
                        temp = temp.Concat<T>(t);
                    }
                    //remove repeating records
                    Query = temp.Distinct<T>();
                }
            }

            //sorting
            Query = Query.OrderBy<T>(grid.sidx,
                grid.sord);

            //count
            long count = Query.LongCount();
            grid.count = count;

            //paging
            var data = Query.Skip((grid.page - 1) * grid.rows).Take(grid.rows).ToArray();

            return data;
        }
    }
}
public enum WhereOperation
{
    [StringValue("eq")]
    Equal,
    [StringValue("ne")]
    NotEqual,
    [StringValue("cn")]
    Contains,
    [StringValue("le")]
    LessEqual,
    [StringValue("ge")]
    GreatEqual,
    [StringValue("bw")]
    BeginsWith
}
#region Class StringEnum

/// <summary>
/// Helper class for working with 'extended' enums using <see cref="StringValueAttribute"/> attributes.
/// http://www.codeproject.com/KB/cs/stringenum.aspx
/// </summary>
public class StringEnum
{
    #region Instance implementation

    private Type _enumType;
    private static Hashtable _stringValues = new Hashtable();

    /// <summary>
    /// Creates a new <see cref="StringEnum"/> instance.
    /// </summary>
    /// <param name="enumType">Enum type.</param>
    public StringEnum(Type enumType)
    {
        if (!enumType.IsEnum)
            throw new ArgumentException(String.Format("Supplied type must be an Enum.  Type was {0}", enumType.ToString()));

        _enumType = enumType;
    }

    /// <summary>
    /// Gets the string value associated with the given enum value.
    /// </summary>
    /// <param name="valueName">Name of the enum value.</param>
    /// <returns>String Value</returns>
    public string GetStringValue(string valueName)
    {
        Enum enumType;
        string stringValue = null;
        try
        {
            enumType = (Enum)Enum.Parse(_enumType, valueName);
            stringValue = GetStringValue(enumType);
        }
        catch (Exception) { }//Swallow!

        return stringValue;
    }

    /// <summary>
    /// Gets the string values associated with the enum.
    /// </summary>
    /// <returns>String value array</returns>
    public Array GetStringValues()
    {
        ArrayList values = new ArrayList();
        //Look for our string value associated with fields in this enum
        foreach (FieldInfo fi in _enumType.GetFields())
        {
            //Check for our custom attribute
            StringValueAttribute[] attrs = fi.GetCustomAttributes(typeof(StringValueAttribute), false) as StringValueAttribute[];
            if (attrs.Length > 0)
                values.Add(attrs[0].Value);

        }

        return values.ToArray();
    }

    /// <summary>
    /// Gets the values as a 'bindable' list datasource.
    /// </summary>
    /// <returns>IList for data binding</returns>
    public IList GetListValues()
    {
        Type underlyingType = Enum.GetUnderlyingType(_enumType);
        ArrayList values = new ArrayList();
        //Look for our string value associated with fields in this enum
        foreach (FieldInfo fi in _enumType.GetFields())
        {
            //Check for our custom attribute
            StringValueAttribute[] attrs = fi.GetCustomAttributes(typeof(StringValueAttribute), false) as StringValueAttribute[];
            if (attrs.Length > 0)
                values.Add(new DictionaryEntry(Convert.ChangeType(Enum.Parse(_enumType, fi.Name), underlyingType), attrs[0].Value));

        }

        return values;

    }

    /// <summary>
    /// Return the existence of the given string value within the enum.
    /// </summary>
    /// <param name="stringValue">String value.</param>
    /// <returns>Existence of the string value</returns>
    public bool IsStringDefined(string stringValue)
    {
        return Parse(_enumType, stringValue) != null;
    }

    /// <summary>
    /// Return the existence of the given string value within the enum.
    /// </summary>
    /// <param name="stringValue">String value.</param>
    /// <param name="ignoreCase">Denotes whether to conduct a case-insensitive match on the supplied string value</param>
    /// <returns>Existence of the string value</returns>
    public bool IsStringDefined(string stringValue, bool ignoreCase)
    {
        return Parse(_enumType, stringValue, ignoreCase) != null;
    }

    /// <summary>
    /// Gets the underlying enum type for this instance.
    /// </summary>
    /// <value></value>
    public Type EnumType
    {
        get { return _enumType; }
    }

    #endregion

    #region Static implementation

    /// <summary>
    /// Gets a string value for a particular enum value.
    /// </summary>
    /// <param name="value">Value.</param>
    /// <returns>String Value associated via a <see cref="StringValueAttribute"/> attribute, or null if not found.</returns>
    public static string GetStringValue(Enum value)
    {
        string output = null;
        Type type = value.GetType();

        if (_stringValues.ContainsKey(value))
            output = (_stringValues[value] as StringValueAttribute).Value;
        else
        {
            //Look for our 'StringValueAttribute' in the field's custom attributes
            FieldInfo fi = type.GetField(value.ToString());
            StringValueAttribute[] attrs = fi.GetCustomAttributes(typeof(StringValueAttribute), false) as StringValueAttribute[];
            if (attrs.Length > 0)
            {
                _stringValues.Add(value, attrs[0]);
                output = attrs[0].Value;
            }

        }
        return output;

    }

    /// <summary>
    /// Parses the supplied enum and string value to find an associated enum value (case sensitive).
    /// </summary>
    /// <param name="type">Type.</param>
    /// <param name="stringValue">String value.</param>
    /// <returns>Enum value associated with the string value, or null if not found.</returns>
    public static object Parse(Type type, string stringValue)
    {
        return Parse(type, stringValue, false);
    }

    /// <summary>
    /// Parses the supplied enum and string value to find an associated enum value.
    /// </summary>
    /// <param name="type">Type.</param>
    /// <param name="stringValue">String value.</param>
    /// <param name="ignoreCase">Denotes whether to conduct a case-insensitive match on the supplied string value</param>
    /// <returns>Enum value associated with the string value, or null if not found.</returns>
    public static object Parse(Type type, string stringValue, bool ignoreCase)
    {
        object output = null;
        string enumStringValue = null;

        if (!type.IsEnum)
            throw new ArgumentException(String.Format("Supplied type must be an Enum.  Type was {0}", type.ToString()));

        //Look for our string value associated with fields in this enum
        foreach (FieldInfo fi in type.GetFields())
        {
            //Check for our custom attribute
            StringValueAttribute[] attrs = fi.GetCustomAttributes(typeof(StringValueAttribute), false) as StringValueAttribute[];
            if (attrs.Length > 0)
                enumStringValue = attrs[0].Value;

            //Check for equality then select actual enum value.
            if (string.Compare(enumStringValue, stringValue, ignoreCase) == 0)
            {
                output = Enum.Parse(type, fi.Name);
                break;
            }
        }

        return output;
    }

    /// <summary>
    /// Return the existence of the given string value within the enum.
    /// </summary>
    /// <param name="stringValue">String value.</param>
    /// <param name="enumType">Type of enum</param>
    /// <returns>Existence of the string value</returns>
    public static bool IsStringDefined(Type enumType, string stringValue)
    {
        return Parse(enumType, stringValue) != null;
    }

    /// <summary>
    /// Return the existence of the given string value within the enum.
    /// </summary>
    /// <param name="stringValue">String value.</param>
    /// <param name="enumType">Type of enum</param>
    /// <param name="ignoreCase">Denotes whether to conduct a case-insensitive match on the supplied string value</param>
    /// <returns>Existence of the string value</returns>
    public static bool IsStringDefined(Type enumType, string stringValue, bool ignoreCase)
    {
        return Parse(enumType, stringValue, ignoreCase) != null;
    }

    #endregion
}

#endregion

#region Class StringValueAttribute

/// <summary>
/// Simple attribute class for storing String Values
/// </summary>
public class StringValueAttribute : Attribute
{
    private string _value;

    /// <summary>
    /// Creates a new <see cref="StringValueAttribute"/> instance.
    /// </summary>
    /// <param name="value">Value.</param>
    public StringValueAttribute(string value)
    {
        _value = value;
    }

    /// <summary>
    /// Gets the value.
    /// </summary>
    /// <value></value>
    public string Value
    {
        get { return _value; }
    }
}

#endregion
